Completed
Push — master ( 318007...44d532 )
by Esaú
01:46
created

hierarchy-helper.js ➔ instanceParameters   B

Complexity

Conditions 6
Paths 8

Size

Total Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
c 1
b 0
f 0
nc 8
dl 0
loc 30
rs 8.439
nop 5

1 Function

Rating   Name   Duplication   Size   Complexity  
A hierarchy-helper.js ➔ ... ➔ ??? 0 1 1
1
// spec/helpers/hierarchy-helper.js
2
"use strict";
3
4
// :: DEPENDENCIES
5
6
const path = require("path");
7
const root = path.dirname(path.dirname(__dirname));
8
9
module.exports = (hierarchy) => {
10
    // check parameters
11
    if (!Array.isArray(hierarchy)) {
12
        throw new Error("hierarchy must be an array");
13
    }
14
15
    // load dependencies
16
    let deps      = hierarchy.map(value => require(path.join(root, value + ".js")));
17
    let klassName = hierarchy.pop();
18
    let Klass     = deps.pop();
19
    const first   = (hierarchy.length === 0);
20
    const third   = (hierarchy.length >= 3);
21
22
    // :: TESTING
23
24
    // test the last class in the hierarchy tree
25
    describe(klassName, () => {
26
27
        // :: INHERITED PROTOTYPE
28
29
        // all inherit from Object
30
        it("should inherit from 'Object'", () => {
31
            expect(new Klass()).toEqual(jasmine.any(Object));
32
        });
33
34
        // check the hierarchy tree
35
        for (let i = 0; i < hierarchy.length; i += 1) {
36
            it("should inherit from '" + hierarchy[i] + "'", () => {
37
                expect(new Klass()).toEqual(jasmine.any(deps[i]));
38
            });
39
        }
40
41
        // check inherited properties
42
        if (!first) {
43
            it("should have a prototype property named 'name'", () => {
44
                expect(Klass.prototype).toHaveString("name");
45
            });
46
47
            it("should have a prototype property named 'message'", () => {
48
                expect(Klass.prototype).toHaveString("message");
49
            });
50
51
            it("should have a prototype property named 'code'", () => {
52
                expect(Klass.prototype).toHaveMember("code");
53
            });
54
        }
55
56
        // check Object methods
57
        it("should have a prototype method named 'toString()'", () => {
58
            expect(Klass.prototype).toHaveMethod("toString");
59
        });
60
61
        // check inherited methods
62
        if (!first) {
63
            it("should have a prototype method named 'native()'", () => {
64
                expect(Klass.prototype).toHaveMethod("native");
65
            });
66
        }
67
68
        // :: EXTENDED PROTOTYPE
69
70
        // check extended properties
71
        if (first) {
72
            it("should have a prototype property named 'name'", () => {
73
                expect(Klass.prototype).toHaveString("name");
74
            });
75
76
            it("should have a prototype property named 'message'", () => {
77
                expect(Klass.prototype).toHaveString("message");
78
            });
79
80
            it("should have a prototype property named 'code'", () => {
81
                expect(Klass.prototype).toHaveMember("code");
82
            });
83
84
            it("should have a prototype method named 'native()'", () => {
85
                expect(Klass.prototype).toHaveMethod("native");
86
            });
87
        }
88
89
        // :: PROTOTYPE VALUES
90
91
        it("should have the 'class' name in the prototype property named 'name'", () => {
92
            expect(Klass.prototype.name).toEqual(klassName);
93
        });
94
95
        it("should have a dummy default value as message", () => {
96
            expect(Klass.prototype.message).toEqual("thrown");
97
        });
98
99
        it("should have a null default value as code", () => {
100
            expect(Klass.prototype.code).toBeNull();
101
        });
102
103
        // :: CONSTRUCTOR
104
105
        it("should instantiate without parameters", () => {
106
            instanceNoParameters(Klass, testNoErrors, third);
107
            testNoErrors(() => new Klass());
108
        });
109
110
        it("should instantiate with parameters", () => {
111
            instanceParameters(Klass, testNoErrors, testNoErrors, testNoErrors, third);
112
        });
113
114
        // use the tests according to the hierarchy level
115
        if (third) {
116
            testThird(Klass);
117
        } else {
118
            test(Klass);
119
        }
120
121
    });
122
123
};
124
125
// Tests that a function doesn't throw any Error
126
function testNoErrors(fn) {
127
    expect(fn).not.toThrowError("parameter 'name' must be a 'string'");
128
    expect(fn).not.toThrowError("parameter 'message' must be a 'string'");
129
    expect(fn).not.toThrowError("parameter 'code' must be a 'number'");
130
}
131
132
// Tests instantiation of a class without parameters (undefined, null or none).
133
function instanceNoParameters(Klass, fn, third) {
134
    let arg1, arg2, arg3, test;
135
    test = (() => new Klass(arg1, arg2, arg3));
136
    for (let i = 0; i < 2; i += 1) {
137
        arg1 = (i % 2 === 0 ? undefined : null);
138
        for (let j = 0; j < 2; j += 1) {
139
            arg2 = (j % 2 === 0 ? undefined : null);
140
            if (third) {
141
                fn(test);
142
            } else {
143
                for (let e = 0; e < 2; e += 1) {
144
                    arg3 = (e % 2 === 0 ? undefined : null);
145
                    fn(test);
146
                }
147
            }
148
        }
149
    }
150
}
151
152
// Tests instantiation of a class with parameters.
153
function instanceParameters(Klass, fn1, fn2, fn3, third) {
154
    let arg1, arg2, arg3, test3, args1, args2, args3;
155
    const test1 = (() => new Klass(arg1));
156
    const test2 = (() => new Klass(arg1, arg2));
157
    if (third) {
158
        test3 = (() => null);
159
        args1 = [undefined, null, Klass.prototype.message];
160
        args2 = [undefined, null, Math.round(Math.random() * 0xFFFFFFFF)];
161
        args3 = [];
162
    } else {
163
        test3 = (() => new Klass(arg1, arg2, arg3));
164
        args1 = [undefined, null, Klass.prototype.name];
165
        args2 = [undefined, null, Klass.prototype.message];
166
        args3 = [undefined, null, Math.round(Math.random() * 0xFFFFFFFF)];
167
    }
168
    for (let i = 0; i < args1.length; i += 1) {
169
        arg1 = args1[i];
170
        fn1(test1);
171
        for (let j = 0; j < args2.length; j += 1) {
172
            arg2 = args2[j];
173
            fn2(test2);
174
            if (!third) {
175
                for (let e = 0; e < args3.length; e += 1) {
176
                    arg3 = args3[e];
177
                    fn3(test3);
178
                }
179
            }
180
        }
181
    }
182
}
183
184
// Loops for each parameter of the Klass constructor.
185
// If the iteration is even, the parameter is defined.
186
// If the iteration is odd, the parameter is null.
187
function instanceDefinedOrNull(Klass, name, message, code, fn1, fn2, fn3, third) {
188
    for (let i = 0; i < 2; i += 1) {
189
        const even1   = (i % 2 === 0);
190
        const arg1    = (even1 ? (third ? message : name) : null);
191
        const source1 = new Klass(arg1);
192
        fn1(source1, even1);
193
        for (let j = 0; j < 2; j += 1) {
194
            const even2   = (j % 2 === 0);
195
            const arg2    = (even2 ? (third ? code : message) : null);
196
            const source2 = new Klass(arg1, arg2);
197
            fn2(source2, even1, even2);
198
            if (!third) {
199
                for (let e = 0; e < 2; e += 1) {
200
                    const even3   = (e % 2 === 0);
201
                    const arg3    = (even3 ? code : null);
202
                    const source3 = new Klass(arg1, arg2, arg3);
203
                    fn3(source3, even1, even2, even3);
204
                }
205
            }
206
        }
207
    }
208
}
209
210
// Loops for each parameter of the Klass constructor using wrong types to test Error throwing.
211
function instanceThrowErrors(Klass, fn1, fn2, fn3, third) {
212
    let arg1, arg2, arg3, test33, test32, test31, test21, test22, test11, len1, len2, len3;
213
    const noStr = [{}, true, false, 42, 3.1416, -42, -3.1416, () => null];
214
    const noNmb = [{}, true, false, '', "qwerty", () => null];
215
    len1        = noStr.length;
216
    if (third) {
217
        len2   = noNmb.length;
218
        len3   = 0;
219
        test33 = test32 = test31 = (() => null);
220
    } else {
221
        len2   = len1;
222
        len3   = noNmb.length;
223
        test33 = (() => new Klass(arg1, arg2, arg3));
224
        test32 = (() => new Klass(null, arg2, arg3));
225
        test31 = (() => new Klass(null, null, arg3));
226
    }
227
    test22 = (() => new Klass(arg1, arg2));
228
    test21 = (() => new Klass(null, arg2));
229
    test11 = (() => new Klass(arg1));
230
    if (typeof Symbol === "function") {
231
        noStr.push(Symbol("symbol"));
232
        noNmb.push(Symbol("symbol"));
233
    }
234
    for (let i = 0; i < len1; i += 1) {
235
        arg1 = noStr[i];
236
        fn1(test11);
237
        for (let j = 0; j < len2; j += 1) {
238
            arg2 = (third ? noNmb[j] : noStr[j]);
239
            fn2(test21, test22);
240
            if (!third) {
241
                for (let e = 0; e < len3; e += 1) {
242
                    arg3 = noNmb[e];
243
                    fn3(test31, test32, test33);
244
                }
245
            }
246
        }
247
    }
248
}
249
250
// Tests classes that are a third level subclass, meaning that they require 2 arguments (message and code).
251
function testThird(Klass) {
252
253
    // :: CONSTRUCTOR
254
255
    it("should throw an Error if 'message' or 'code' are invalid parameters", () => {
256
        instanceThrowErrors(Klass, (test11) => {
257
            expect(test11).toThrowError("parameter 'message' must be a 'string'");
258
        }, (test21, test22) => {
259
            expect(test21).toThrowError("parameter 'code' must be a 'number'");
260
            expect(test22).toThrowError("parameter 'message' must be a 'string'");
261
        }, null, true);
262
    });
263
264
    // :: MEMBER PROPERTIES
265
266
    const message = "asdf";
267
    const code    = Math.round(Math.random() * 0xFFFFFFFF);
268
269
    it("should have all correct properties once instantiated", () => {
270
        instanceDefinedOrNull(Klass, null, message, code, (instance, even) => {
271
            if (even) {
272
                expect(instance.name).toEqual(Klass.prototype.name);
273
                expect(instance.message).toEqual(message);
274
            } else {
275
                expect(instance.name).toEqual(Klass.prototype.name);
276
                expect(instance.message).toEqual(Klass.prototype.message);
277
            }
278
            expect(instance.code).toBeNull();
279
        }, (instance, even1, even2) => {
280
            expect(instance.name).toEqual(Klass.prototype.name);
281
            expect(instance.message).toEqual(even1 ? message : Klass.prototype.message);
282
            expect(instance.code).toEqual(even2 ? code : null);
283
        }, null, true);
284
    });
285
286
    // :: MEMBER METHODS
287
288
    it("#toString()", () => {
289
        instanceDefinedOrNull(Klass, null, message, code, (instance, even) => {
290
            let exp;
291
            if (even) {
292
                exp = Klass.prototype.name + ": " + message + '.';
293
            } else {
294
                exp = Klass.prototype.name + ": " + Klass.prototype.message + '.';
295
            }
296
            expect(instance.toString()).toEqual(exp);
297
        }, (instance, even1, even2) => {
298
            let exp;
299
            exp  = Klass.prototype.name;
300
            exp += (even2 ? " (0x" + code.toString(16) + "):" : ':' ) + ' ';
301
            exp += (even1 ? message : Klass.prototype.message) + '.';
302
            expect(instance.toString()).toEqual(exp);
303
        }, null, true);
304
    });
305
306
    it("#native()", () => {
307
        instanceDefinedOrNull(Klass, null, message, code, (instance, even) => {
308
            const exp = (even ? message : Klass.prototype.message);
309
            expect(instance.native()).toEqual(new Error(exp));
310
        }, (instance, even1) => {
311
            const exp = (even1 ? message : Klass.prototype.message);
312
            expect(instance.native()).toEqual(new Error(exp));
313
        }, null, true);
314
    });
315
316
}
317
318
// Tests classes that require 3 arguments (name, message and code).
319
function test(Klass) {
320
321
    // :: CONSTRUCTOR
322
323
    it("should throw an Error if 'message' or 'code' are invalid parameters", () => {
324
        instanceThrowErrors(Klass, (test11) => {
325
            expect(test11).toThrowError("parameter 'name' must be a 'string'");
326
        }, (test21, test22) => {
327
            expect(test22).toThrowError("parameter 'name' must be a 'string'");
328
            expect(test21).toThrowError("parameter 'message' must be a 'string'");
329
        }, (test31, test32, test33) => {
330
            expect(test33).toThrowError("parameter 'name' must be a 'string'");
331
            expect(test32).toThrowError("parameter 'message' must be a 'string'");
332
            expect(test31).toThrowError("parameter 'code' must be a 'number'");
333
        }, false);
334
    });
335
336
    // :: MEMBER PROPERTIES
337
338
    const name    = "qwerty";
339
    const message = "asdf";
340
    const code    = Math.round(Math.random() * 0xFFFFFFFF);
341
342
    it("should have all correct properties once instantiated", () => {
343
        instanceDefinedOrNull(Klass, name, message, code, (instance, even) => {
344
            if (even) {
345
                expect(instance.name).toEqual(name);
346
                expect(instance.message).toEqual(Klass.prototype.message);
347
            } else {
348
                expect(instance.name).toEqual(Klass.prototype.name);
349
                expect(instance.message).toEqual(Klass.prototype.message);
350
            }
351
            expect(instance.code).toBeNull();
352
        }, (instance, even1, even2) => {
353
            expect(instance.name).toEqual(even1 ? name : Klass.prototype.name);
354
            expect(instance.message).toEqual(even2 ? message : Klass.prototype.message);
355
            expect(instance.code).toBeNull();
356
        }, (instance, even1, even2, even3) => {
357
            expect(instance.name).toEqual(even1 ? name : Klass.prototype.name);
358
            expect(instance.message).toEqual(even2 ? message : Klass.prototype.message);
359
            expect(instance.code).toEqual(even3 ? code : null);
360
        }, false);
361
    });
362
363
    // :: MEMBER METHODS
364
365
    it("#toString()", () => {
366
        instanceDefinedOrNull(Klass, name, message, code, (instance, even) => {
367
            let exp;
368
            if (even) {
369
                exp = name + ": " + Klass.prototype.message + '.';
370
            } else {
371
                exp = Klass.prototype.name + ": " + Klass.prototype.message + '.';
372
            }
373
            expect(instance.toString()).toEqual(exp);
374
        }, (instance, even1, even2) => {
375
            let exp;
376
            exp  = (even1 ? name : Klass.prototype.name) + ':';
377
            exp += ' ' + (even2 ? message : Klass.prototype.message) + '.';
378
            expect(instance.toString()).toEqual(exp);
379
        }, (instance, even1, even2, even3) => {
380
            let exp;
381
            exp  = (even1 ? name : Klass.prototype.name);
382
            exp += (even3 ? " (0x" + code.toString(16) + "):" : ':') + ' ';
383
            exp += (even2 ? message : Klass.prototype.message) + '.';
384
            expect(instance.toString()).toEqual(exp);
385
        }, false);
386
    });
387
388
    it("#native()", () => {
389
        instanceDefinedOrNull(Klass, name, message, code, (instance) => {
390
            const exp = Klass.prototype.message;
391
            expect(instance.native()).toEqual(new Error(exp));
392
        }, (instance, even1, even2) => {
393
            const exp = (even2 ? message : Klass.prototype.message);
394
            expect(instance.native()).toEqual(new Error(exp));
395
        }, (instance, even1, even2) => {
396
            const exp = (even2 ? message : Klass.prototype.message);
397
            expect(instance.native()).toEqual(new Error(exp));
398
        }, false);
399
    });
400
401
}